#include "ScenesManager.h"
#include "source/input/InputSourceManager.h"
#include "source/output/OutputSourceManager.h"
#include "mixer/VideoMixerManager.h"
#include "mixer/AudioMixerManager.h"
#include "SqlTableHelper.h"
#include "json.h"
#include "Util/logc.h"

namespace Media{
    ScenesManager::Ptr ScenesManager::Instance(){
        static auto instance = std::make_shared<ScenesManager>();
        return instance;
    }

    ScenesManager::ScenesManager(){
        m_sql = SqlTableOp::Instance();
        init();
    }
    
    ScenesManager::~ScenesManager()= default;

    bool ScenesManager::init(){
        //init m_scenes from read sqlite3
        std::vector<scenesTable> tbs;
        if(!m_sql->GetAllTable<scenesTable>(tbs)){
            return false;
        }

        std::vector<outputSourceTable> outTables;
        std::map<uint32_t, outputSourceTable> outSourceMap;
        if(!m_sql->GetAllTable<outputSourceTable>(outTables)){
            return false;
        }

        for(const auto& tb:outTables){
            outSourceMap[tb.m_id] = tb;
        }

        for(const auto& tb:tbs){
            auto sc = std::make_shared<Scenes>(tb.m_name);
            std::map<uint32_t, VideoScenesInItemConfig> vIn;
            std::map<uint32_t, AudioScenesInItemConfig> aIn; 

            VideoScenesOutItemConfig vOutConfig;
            AudioScenesOutItemConfig aOutConfig;
            std::vector<uint32_t> vOutSources;
            std::vector<uint32_t> aOutSources;

            if(toScenesInItemConfig(tb, vIn, aIn)){
                for(auto [key, val]:vIn){
                    sc->AddVideoInItem(key, val);
                }
                for(auto [key, val]:aIn){
                    sc->AddAudioInItem(key, val);
                }
            }

            //add video output
            if(toVideoScenesOutItemConfig(tb, vOutConfig, vOutSources)){
                sc->SetVideoOutConfig(vOutConfig);
                for(auto out:vOutSources){
                    if(outSourceMap.find(out) != outSourceMap.end()){
                        sc->AddVideoOutSource(out, outSourceMap[out].m_name);
                    }
                }
            }
            //add audio output
            if(toAudioScenesOutItemConfig(tb,aOutConfig, aOutSources)){
                sc->SetAudioOutConfig(aOutConfig);
                for(auto out:aOutSources){
                    if(outSourceMap.find(out) != outSourceMap.end()){
                        sc->AddAudioOutSource(out, outSourceMap[out].m_name);
                    }
                }
            }
            
            m_scenes[tb.m_id] = sc;
            if(tb.m_enabled){
                StartScenes(tb.m_id);
            }
        }

        return true;
    }

    bool ScenesManager::AddScenes(const std::string& scenesName, ScenesId& id, 
        std::string& errinfo){
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        //1. determine whether it already exists, check to map and sql
        //check map
        for(auto [key, value]:m_scenes){
            if(value->GetName() == scenesName){
                id = key;
                errinfo = "this scene with the same name already exists and duplicate additions are not allowed"; 
                return false;
            }
        }

        //2. insert to sql, and return scenesId
        scenesTable tb = {};
        tb.m_name = scenesName;
        if(!m_sql->InsertTable<scenesTable>(tb, id)){
            errinfo = "AddScenes fail";
            return false;
        }

        //3. storage to map
        m_scenes[id] = std::make_shared<Scenes>(scenesName);
        errinfo = "AddScenes success";
        return true;
    }

    bool ScenesManager::DelScenes(ScenesId id){
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        if(m_scenes.find(id) == m_scenes.end() || m_scenes[id]->IsRunning()){
            errorf("DelScenes fail");
            return false;
        }
        //delete items in scenes
        m_sql->DeleteItemsFromScenesId<scenesItemTable>(id);
        m_scenes.erase(id);
        return m_sql->DelTable<scenesTable>(id);
    }

    bool ScenesManager::GetOneScenes(ScenesId id, Scenes::Ptr& scenes){
        LOCK_CHECK(id)
        scenes = m_scenes[id];
        return true;
    }

    bool ScenesManager::GetAllScenes(std::map<ScenesId, Scenes::Ptr>& allScenes){
        std::lock_guard<std::recursive_mutex> guard(m_mux);
        allScenes = m_scenes;
        return true;
    }


    bool ScenesManager::AddVideoInItem(ScenesId id, const VideoScenesInItemConfig& config,
         uint32_t& itemId){
        LOCK_CHECK(id)

        struct scenesItemTable tb;
        toVideoItemTable(config, tb);
        tb.m_scenesId = id;
        if(m_sql->InsertTable<scenesItemTable>(tb, itemId)){
            return m_scenes[id]->AddVideoInItem(itemId, config);
        }
        return false;
    }
    bool ScenesManager::SetVideoInItem(ScenesId id, const VideoScenesInItemConfig& config, 
        const uint32_t& itemId){
        LOCK_CHECK(id)

        struct scenesItemTable tb;
        toVideoItemTable(config, tb);
        tb.m_id = itemId;
        tb.m_scenesId = id;
        if(m_sql->UpdateTable<scenesItemTable>(tb)){
            return m_scenes[id]->SetVideoInItem(itemId, config);
        }

        return false;
    }
    bool ScenesManager::DelVideoInItem(ScenesId id, const uint32_t& itemId){
        LOCK_CHECK(id)

        if(m_sql->DelTable<scenesItemTable>(itemId)){
            return m_scenes[id]->DelVideoInItem(itemId);
        }
        return false;
    }
    bool ScenesManager::GetOneVideoInItem(ScenesId id, const uint32_t& itemId, 
        VideoScenesInItemConfig& config){
        LOCK_CHECK(id)

        return m_scenes[id]->GetVideoInItem(itemId, config);
    }
    bool ScenesManager::GetAllVideoInItem(ScenesId id, 
        std::map<Scenes::ItemId, VideoScenesInItemConfig>& items){
        LOCK_CHECK(id)

        items = m_scenes[id]->GetAllVideoInItem();
        return true;
    }

    bool ScenesManager::SetVideoOutConfig(ScenesId id, const VideoScenesOutItemConfig& config){
        LOCK_CHECK(id)
        scenesTable stb;
        if(!m_sql->GetOneTable<scenesTable>(id, stb)){
            return false;
        }

        toVideoOutTableString(config, stb.m_videoOutputConfig);
        if(m_sql->UpdateTable<scenesTable>(stb)){
            return m_scenes[id]->SetVideoOutConfig(config);
        }
        return false;
    }

    bool ScenesManager::GetVideoOutConfig(ScenesId id, VideoScenesOutItemConfig& config){
        LOCK_CHECK(id)
        return m_scenes[id]->GetVideoOutConfig(config);
    }

    bool ScenesManager::AddVideoOutSource(ScenesId id, uint32_t sourceId){
        LOCK_CHECK(id)
        outputSourceTable tb;
        if(!m_sql->GetOneTable<outputSourceTable>(sourceId, tb)){
            return false;
        }
        
        scenesTable stb;
        if(!m_sql->GetOneTable<scenesTable>(id, stb)) {
            return false;
        }

        if(toAddOutSourceTableString(sourceId, stb.m_videoOutputConfig)){
            if(!m_sql->UpdateTable<scenesTable>(stb)){
                return false;
            }
        }else{
            return false;
        }

        return m_scenes[id]->AddVideoOutSource(sourceId, tb.m_name);
    }

    bool ScenesManager::RemoveVideoOutSource(ScenesId id, uint32_t sourceId){
        LOCK_CHECK(id)

        //update scenesTable
        scenesTable stb;
        if(!m_sql->GetOneTable<scenesTable>(id, stb)) {
            return false;
        }

        if(toRemoveOutSourceTableString(sourceId, stb.m_videoOutputConfig)){
            if(!m_sql->UpdateTable<scenesTable>(stb)){
                return false;
            }
        }else{
            return false;
        }
        return m_scenes[id]->RemoveVideoOutSource(sourceId);
    }
    
    //audio item operate
    bool ScenesManager::AddAudioInItem(ScenesId id, const AudioScenesInItemConfig& config,
         uint32_t& itemId){
        LOCK_CHECK(id)

        struct scenesItemTable tb;
        toAudioItemTable(config, tb);
        if(m_sql->InsertTable<scenesItemTable>(tb, itemId)){
            return m_scenes[id]->AddAudioInItem(itemId, config);
        }
        return false;
    }
    bool ScenesManager::SetAudioInItem(ScenesId id, const AudioScenesInItemConfig& config, 
        const uint32_t& itemId){
        LOCK_CHECK(id)

        struct scenesItemTable tb;
        toAudioItemTable(config, tb);
        tb.m_id = itemId;
        if(m_sql->UpdateTable<scenesItemTable>(tb)){
            return m_scenes[id]->SetAudioInItem(itemId, config);
        }

        return false;
    }
    bool ScenesManager::DelAudioInItem(ScenesId id, const uint32_t& itemId){
        LOCK_CHECK(id)
        if(m_sql->DelTable<scenesItemTable>(itemId)){
            return m_scenes[id]->DelAudioInItem(itemId);
        }
        return false;
    }
    bool ScenesManager::GetOneAudioInItem(ScenesId id, const uint32_t& itemId, 
        AudioScenesInItemConfig& config){
        LOCK_CHECK(id)
        return m_scenes[id]->GetAudioInItem(itemId, config);
    }
    bool ScenesManager::GetAllAudioInItem(ScenesId id, 
                    std::map<Scenes::ItemId, AudioScenesInItemConfig>& items){
        LOCK_CHECK(id)
        items = m_scenes[id]->GetAllAudioInItem();
        return true;
    }

    bool ScenesManager::SetAudioOutConfig(ScenesId id, const AudioScenesOutItemConfig& config){
        LOCK_CHECK(id)
        scenesTable stb;
        if(!m_sql->GetOneTable<scenesTable>(id, stb)){
            return false;
        }

        toAudioOutTableString(config, stb.m_audioOutputConfig);
        if(m_sql->UpdateTable<scenesTable>(stb)){
            return m_scenes[id]->SetAudioOutConfig(config);
        }
        return false;
    }
    bool ScenesManager::GetAudioOutConfig(ScenesId id, AudioScenesOutItemConfig& config){
        LOCK_CHECK(id)
        return m_scenes[id]->GetAudioOutConfig(config);
    }
    bool ScenesManager::AddAudioOutSource(ScenesId id, uint32_t sourceId){
        LOCK_CHECK(id)
        outputSourceTable tb;
        if(!m_sql->GetOneTable<outputSourceTable>(sourceId, tb)){
            return false;
        }
        //update scenesTable
        scenesTable stb;
        if(!m_sql->GetOneTable<scenesTable>(id, stb)) {
            return false;
        }

        if(toAddOutSourceTableString(sourceId, stb.m_audioOutputConfig)){
            if(!m_sql->UpdateTable<scenesTable>(stb)){
                return false;
            }
        }else{
            return false;
        }
        return m_scenes[id]->AddAudioOutSource(sourceId, tb.m_name);
    }
    bool ScenesManager::RemoveAudioOutSource(ScenesId id, uint32_t sourceId){
        LOCK_CHECK(id)

        //update scenesTable
        scenesTable stb;
        if(!m_sql->GetOneTable<scenesTable>(id, stb)) {
            return false;
        }

        if(toRemoveOutSourceTableString(sourceId, stb.m_audioOutputConfig)){
            if(!m_sql->UpdateTable<scenesTable>(stb)){
                return false;
            }
        }else{
            return false;
        }

        return m_scenes[id]->RemoveAudioOutSource(sourceId);
    }
    bool ScenesManager::StartScenes(ScenesId id){
        LOCK_CHECK(id)
        if(m_scenes[id]->Start()){
            return m_sql->SetScenesState(id, true);
        }
        return false;
    }
    bool ScenesManager::StopScenes(ScenesId id){
        LOCK_CHECK(id)
        if(m_scenes[id]->Stop()){
            return m_sql->SetScenesState(id, false);
        }
        return false;
    }

    bool ScenesManager::SetFrameOutCb(ScenesId id, std::function<void (const FrameCommon::Frame::Ptr& frame)> cb){
        LOCK_CHECK(id)
        return m_scenes[id]->SetFrameOutCb(cb);
    }
}